package org.springblade.modules.client;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.springblade.common.exception.TrainingServiceException;
import org.springblade.common.constant.RedisKeyConstant;
import org.springblade.common.enums.TrainingResultCode;
import org.springblade.core.mp.support.Condition;
import org.springblade.core.mp.support.Query;
import org.springblade.core.redis.cache.BladeRedis;
import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.CollectionUtil;
import org.springblade.core.tool.utils.DigestUtil;
import org.springblade.modules.course.entity.CourseEntity;
import org.springblade.modules.course.service.ICourseService;
import org.springblade.modules.log.service.IIntegralLogService;
import org.springblade.modules.log.vo.IntegralLogVO;
import org.springblade.modules.material.context.CalculateScoreContext;
import org.springblade.modules.material.entity.*;
import org.springblade.modules.material.service.*;
import org.springblade.modules.material.strategy.CalculateScoreStrategy;
import org.springblade.modules.material.strategy.impl.*;
import org.springblade.modules.material.vo.PaperReleaseDetailVO;
import org.springblade.modules.material.vo.PaperReleaseVO;
import org.springblade.modules.material.wrapper.PaperReleaseWrapper;
import org.springblade.modules.personal.service.IUserBaseService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.util.*;
import java.util.stream.Collectors;

import static org.springblade.common.constant.CommonConstant.*;

/**
 * 客户端考试相关接口
 */
@RestController
@AllArgsConstructor
@RequestMapping("blade-client/exam")
@Api(value = "客户端考试相关接口", tags = "考试相关接口")
public class ClientReleaseController {

	private final IPaperReleaseService releaseService;
	private final IPaperService paperService;
	private final IAnswerService answerService;
	private final IAnswerOptionService answerOptionService;
	private final BladeRedis bladeRedis;
	private final ICourseService courseService;
	private final IReleaseExamUserService releaseExamUserService;
	private final IIntegralLogService integralLogService;

	/**
	 * 学生查看我的考试记录
	 * 第一层显示最高成绩
	 * 第二层显示考试成绩明细
	 */
	@GetMapping("/student-query-exam")
	@ApiOperationSupport(order = 1)
	@ApiOperation(value = "考试记录列表", notes = "传入paperRelease")
	public R studentQueryExam(PaperReleaseVO paperRelease, Query query) {
		Long userId = AuthUtil.getUserId();

		//根据用户答卷记录分组 得出已做答几门考试
		List<PaperAnswerEntity> paperAnswerEntityList = answerService.list(Wrappers.<PaperAnswerEntity>query().lambda()
			.eq(PaperAnswerEntity::getUserId, userId));
		if (CollectionUtil.isEmpty(paperAnswerEntityList)) return R.success("暂无考试记录");
		//试卷发布的ids
		List<Long> paperReleaseIds = paperAnswerEntityList.stream().map(PaperAnswerEntity::getPaperReleaseId).distinct().collect(Collectors.toList());
		LambdaQueryWrapper<PaperReleaseEntity> paperReleaseEntityLambdaQueryWrapper=new LambdaQueryWrapper<>();
		paperReleaseEntityLambdaQueryWrapper.in(PaperReleaseEntity::getId,paperReleaseIds);
		if (StringUtils.isNotBlank(paperRelease.getReleaseName())) paperReleaseEntityLambdaQueryWrapper.like(PaperReleaseEntity::getReleaseName,paperRelease.getReleaseName());
		if (StringUtils.isNotBlank(paperRelease.getSearchStartTime())) paperReleaseEntityLambdaQueryWrapper.ge(PaperReleaseEntity::getStartTime,paperRelease.getSearchStartTime());
		if (StringUtils.isNotBlank(paperRelease.getSearchEndTime())) paperReleaseEntityLambdaQueryWrapper.le(PaperReleaseEntity::getEndTime,paperRelease.getSearchEndTime());
		IPage<PaperReleaseEntity> page = releaseService.page(Condition.getPage(query), paperReleaseEntityLambdaQueryWrapper);
		IPage<PaperReleaseVO> paperReleaseVOIPage = PaperReleaseWrapper.build().pageVO(page);

		paperReleaseVOIPage.getRecords().forEach(pageVo->{
			List<PaperAnswerEntity> list = answerService.list(Wrappers.<PaperAnswerEntity>query().lambda()
				.eq(PaperAnswerEntity::getUserId, userId).eq(PaperAnswerEntity::getPaperReleaseId, pageVo.getId()).orderByDesc(PaperAnswerEntity::getCreateTime));
			//设置最高分作为默认展示的分数
			list.stream().max(Comparator.comparing(PaperAnswerEntity::getScore)).ifPresent(e->pageVo.setScore(e.getScore()));
			pageVo.setChildren(list);
		});

		return R.data(paperReleaseVOIPage);
	}


	/**
	 * 学生查看我的考试
	 */
	@GetMapping("/student-exam-myself")
	@ApiOperationSupport(order = 1)
	@ApiOperation(value = "我的考试列表", notes = "传入paperRelease")
	public R studentExamMyself(PaperReleaseVO paperRelease, Query query) {
		paperRelease.setUserId(AuthUtil.getUserId());
		paperRelease.setExamineType(2);//考核类型为考试
		IPage<PaperReleaseVO> pages = releaseService.studentExamMyself(Condition.getPage(query), paperRelease);
		Date date = new Date();
		pages.getRecords().forEach(item -> {
			//已考试次数
			long examCount = answerService.count(Wrappers.<PaperAnswerEntity>query().lambda()
				.eq(PaperAnswerEntity::getExamineType, 2)
				.eq(PaperAnswerEntity::getPaperReleaseId, item.getId())
				.eq(PaperAnswerEntity::getUserId, AuthUtil.getUserId()));

			item.setResidueNum(item.getAllowedRetakesQuantity() + 1 - examCount);
			if (item.getStartTime().after(date)) {//开始时间在当前时间之后 表示未开始
				item.setExamStatus("未开始");
			} else if (item.getStartTime().before(date) && item.getEndTime().after(date)) {//开始时间在当前时间之前 且当前时间在结束时间之前 表示进行中
				item.setExamStatus("进行中");
			} else {
				item.setExamStatus("已结束");
			}
		});
		return R.data(pages);
	}


	/**
	 * 学生进入考试
	 * PaperAnswerEntity status= 0 考试中；status= 1 提交试卷
	 */
	@GetMapping("/student-enter-exam")
	@ApiOperationSupport(order = 2)
	@ApiOperation(value = "学生进入考试", notes = "")
	public R studentEnterExam(PaperReleaseEntity paperReleaseEntity) {
		//查询考试是否已经开始
		PaperReleaseEntity releaseEntity = releaseService.getById(paperReleaseEntity.getId());
		if (new Date().before(releaseEntity.getStartTime())) {
			return R.success(TrainingResultCode.SERVICE_FAILURE, "考试未开始");
		}
		if (null != releaseEntity.getLatestTime()) {
			if (releaseEntity.getLatestTime().before(new Date())) throw new TrainingServiceException("已超过考场最晚进入时间");
		}

//		long deadlineTime = releaseEntity.getStartTime().getTime() + 30 * 60 * 1000;//进入考试的截止时间 30分钟 目前不需要
//		if (new Date().after(releaseEntity.getStartTime()) && new Date().after(new Date(deadlineTime))) {// 考试已经开始 且 当前时间超过考试时间30分钟
//			return R.success(TrainingResultCode.SERVICE_FAILURE,"您已超过最晚进入考试时间，不被允许参加考试");
//		}
		ReleaseExamUserEntity releaseExamUser = releaseExamUserService.getOne(Wrappers.<ReleaseExamUserEntity>query().lambda()
			.eq(ReleaseExamUserEntity::getUserId, AuthUtil.getUserId())
			.eq(ReleaseExamUserEntity::getReleaseId, paperReleaseEntity.getId()));
		if (0 == releaseExamUser.getPermit()) return R.success(TrainingResultCode.SERVICE_FAILURE, "你未被授权进入该场考试");


		if (new Date().after(releaseEntity.getEndTime())) {// 考试已结束
			return R.success(TrainingResultCode.SERVICE_FAILURE, "此考试已结束，不允许进入考试");
		}

		List<PaperAnswerEntity> oldPaperAnswerList = answerService.list(Wrappers.<PaperAnswerEntity>query().lambda()
			.eq(PaperAnswerEntity::getUserId, AuthUtil.getUserId())
			.eq(PaperAnswerEntity::getPaperReleaseId, releaseEntity.getId())
			.eq(PaperAnswerEntity::getExamineType, 2));
		if (CollectionUtil.isNotEmpty(oldPaperAnswerList)) {
			if (oldPaperAnswerList.size() > releaseEntity.getAllowedRetakesQuantity()) {//如果作答次数 超过 可重考次数，则不能进入考场。例 可重考次数为1 ，则可以进入考试两次
				return R.success(TrainingResultCode.SERVICE_FAILURE, "你已超过可重考次数,不可进入考场");
			}
		}


		PaperAnswerEntity paperAnswerEntity = new PaperAnswerEntity();
		paperAnswerEntity.setPaperReleaseId(releaseEntity.getId());
		paperAnswerEntity.setUserId(AuthUtil.getUserId());
		paperAnswerEntity.setExamStartTime(new Date());
		if (0 == releaseEntity.getIsNeedReview()) paperAnswerEntity.setIsReview(1);//如果该场考试不需要批阅，则设置已批阅，批阅者为空
		//表示考试中
		paperAnswerEntity.setStatus(0);
		paperAnswerEntity.setScore((float) 0);
		paperAnswerEntity.setExamineType(2);//表示考试
		paperAnswerEntity.setExamStartTime(new Date());
		answerService.save(paperAnswerEntity);
		return R.data(paperAnswerEntity.getId());
	}

	/**
	 * 获取考试试卷
	 */
	@GetMapping("/get-exam-paper")
	@ApiOperationSupport(order = 3)
	@ApiOperation(value = "获取考试试卷", notes = "传入releaseId")
	public R examPaper(Long releaseId) {
		PaperReleaseEntity paperReleaseEntity = releaseService.getById(releaseId);
		PaperReleaseVO examPaper = releaseService.getExamPaper(releaseId);

		if (null == paperReleaseEntity.getIsRule() || 0 == paperReleaseEntity.getIsRule()) {//非规则组卷的试题
			examPaper.setPaperReleaseDetailList(paperService.sortExamPaperDetailByQuestionType(examPaper.getPaperReleaseDetailList()));
		} else {//规则组卷的试题
			examPaper = releaseService.getRuleExamPaper(releaseId);
		}

		return R.data(examPaper);
	}


	/**
	 * 获取提交考试的权限 避免重复提交
	 */
	@GetMapping("/get-submit-paper-token")
	@ApiOperationSupport(order = 4)
	@ApiOperation(value = "获取提交的权限", notes = "传入releaseId")
	public R getSubmitPaperToken(Long releaseId, Long paperAnswerId) {
		PaperReleaseEntity releaseEntity = releaseService.getById(releaseId);
		if (null == releaseEntity) throw new TrainingServiceException("未找到该场考试");
		String key = DigestUtil.encrypt(RedisKeyConstant.EXAM_KEY_PREFIX + releaseId.toString() + AuthUtil.getUserId().toString() + System.currentTimeMillis());
		bladeRedis.set(key, paperAnswerId);
		return R.data(key);
	}


	/**
	 * 学生提交试卷
	 * 自动计算除简答题外的分数
	 */
	@PostMapping("/submit-paper")
	@ApiOperationSupport(order = 13)
	@ApiOperation(value = "学生提交试卷", notes = "传入整张试卷")
	@Transactional(rollbackFor = Exception.class)
	public R autoCalculateScore(@RequestBody @Valid PaperReleaseVO paperRelease) {
		Long paperAnswerId = bladeRedis.get(paperRelease.getSubmitToken());
		if (null == paperAnswerId) throw new TrainingServiceException("请勿重复提交");

		//少选是否给一半分数
		Integer multipleScoreSetting = paperRelease.getMultipleScoreSetting();

		//试卷分数
		int score = 0;

		//考试人员的作答及试题
		List<PaperReleaseDetailVO> paperReleaseDetailVOList = paperRelease.getPaperReleaseDetailList();

		//考试人员的作答选项
		List<PaperAnswerOptionEntity> paperAnswerOptionEntityList = new ArrayList<>();

		//将题目按照题目类型分组
		Map<Integer, List<PaperReleaseDetailVO>> releaseDetailGroupMap = paperReleaseDetailVOList.stream().collect(Collectors.groupingBy(PaperReleaseDetailVO::getQuestionType));

		//计算分数
		for (Integer key : releaseDetailGroupMap.keySet()) { //按照题目类型分组处理
			//策略模式
			CalculateScoreStrategy strategy = null;
			switch (key) {
				case SINGLE:
					strategy = new CalculateSingleChoice();
					break;
				case MULTIPLE:
					strategy = new CalculateMultipleChoice();
					break;
				case JUDGE:
					strategy = new CalculateJudgeQuestion();
					break;
				case FILL:
					strategy = new CalculateClozeQuestion();
					break;
				case SHORT:
					strategy = new CalculateShortAnswer();
					break;
				default:
					throw new TrainingServiceException("没有匹配到题目类型");
			}
			CalculateScoreContext context = new CalculateScoreContext(strategy);
			score += context.executeStrategy(releaseDetailGroupMap.get(key), paperAnswerOptionEntityList, paperAnswerId, multipleScoreSetting);
		}

		//保存用户作答选项记录
		answerOptionService.saveBatch(paperAnswerOptionEntityList);

		//修改用户的分数
		List<PaperAnswerEntity> paperAnswerEntityList = answerService.list(Wrappers.<PaperAnswerEntity>query().lambda()
			.eq(PaperAnswerEntity::getUserId, AuthUtil.getUserId())
			.eq(PaperAnswerEntity::getExamineType, 2)
			.orderByDesc(PaperAnswerEntity::getExamStartTime));
		if (CollectionUtil.isNotEmpty(paperAnswerEntityList)) {
			//根据创建时间降序，第一条是最新的作答记录
			PaperAnswerEntity paperAnswerEntity = paperAnswerEntityList.get(0);

			paperAnswerEntity.setScore((float) score);
			//表示已交卷
			paperAnswerEntity.setStatus(1);
			paperAnswerEntity.setExamEndTime(new Date());
			answerService.updateById(paperAnswerEntity);

			//判断该学员成绩是否合格
			if (score >= paperRelease.getPassingScore()) { //如果合格，发放该课程的学时和积分
				PaperReleaseEntity releaseEntity = releaseService.getById(paperRelease.getId());
				IntegralLogVO vo = integralLogService.generalIntegralByExam(releaseEntity);
				vo.setUserId(AuthUtil.getUserId());
				integralLogService.addIntegral(vo);
			}
		}

		return R.data(bladeRedis.del(paperRelease.getSubmitToken()));
	}

	/**
	 * 学生进入课程测试
	 * PaperAnswerEntity status= 0 考试中；status= 1 提交试卷
	 */
	@GetMapping("/student-enter-course-exam")
	@ApiOperationSupport(order = 17)
	@ApiOperation(value = "学生进入课程测试", notes = "")
	public R studentEnterCourseExam(PaperReleaseEntity paperReleaseEntity) {
		//所有课程视频及课后练习都已完成才可以进入
		if (!courseService.checkCourseComplete(paperReleaseEntity.getCourseId(), AuthUtil.getUserId()))
			throw new TrainingServiceException("该课程的学习任务未完成,不可进入课程测试!");

		//查询该场课程测试
		PaperReleaseEntity releaseEntity = releaseService.getOne(Wrappers.<PaperReleaseEntity>query().lambda()
			.eq(PaperReleaseEntity::getCourseId, paperReleaseEntity.getCourseId()).eq(PaperReleaseEntity::getExamineType, 1));
		if (null == releaseEntity) throw new TrainingServiceException("该课程未设置课程测试,请联系管理员");

		List<PaperAnswerEntity> paperAnswerEntityList = answerService.list(Wrappers.<PaperAnswerEntity>query().lambda()
			.eq(PaperAnswerEntity::getUserId, AuthUtil.getUserId())
			.eq(PaperAnswerEntity::getExamineType, 1)
			.orderByDesc(PaperAnswerEntity::getScore));
		if (CollectionUtil.isNotEmpty(paperAnswerEntityList) && paperAnswerEntityList.get(0).getScore() >= releaseEntity.getPassingScore())
			throw new TrainingServiceException("你已通过该课程测试,请勿重复测试");

		PaperAnswerEntity paperAnswerEntity = new PaperAnswerEntity();
		paperAnswerEntity.setPaperReleaseId(releaseEntity.getId());
		paperAnswerEntity.setUserId(AuthUtil.getUserId());
		paperAnswerEntity.setExamStartTime(new Date());
		paperAnswerEntity.setIsReview(1);//课程测试，直接设置为已批阅，因为没有简答题
		//表示考试中
		paperAnswerEntity.setStatus(0);
		paperAnswerEntity.setScore((float) 0);
		paperAnswerEntity.setExamineType(1);//表示课程测试
		paperAnswerEntity.setExamStartTime(new Date());
		answerService.save(paperAnswerEntity);
		return R.data(paperAnswerEntity.getId());
	}

	/**
	 * 获取考试试卷
	 */
	@PostMapping("/get-exam-course-paper")
	@ApiOperationSupport(order = 18)
	@ApiOperation(value = "获取课程测试试卷", notes = "传入课程id")
	public R examCoursePaper(Long courseId) {
		PaperReleaseEntity releaseEntity = releaseService.getOne(Wrappers.<PaperReleaseEntity>query().lambda()
			.eq(PaperReleaseEntity::getCourseId, courseId).eq(PaperReleaseEntity::getExamineType, 1));
		if (null == releaseEntity) throw new TrainingServiceException("该课程未设置课程测试,请联系管理员");
		PaperReleaseVO examPaper = releaseService.getExamPaper(releaseEntity.getId());

		if (null == releaseEntity.getIsRule() || 0 == releaseEntity.getIsRule()) {//非规则组卷的试题
			examPaper.setPaperReleaseDetailList(paperService.sortExamPaperDetailByQuestionType(examPaper.getPaperReleaseDetailList()));
		} else {//规则组卷的试题
			examPaper = releaseService.getRuleExamPaper(releaseEntity.getId());
		}

		return R.data(examPaper);
	}

	/**
	 * 获取课程测试提交的权限 避免重复提交
	 */
	@GetMapping("/get-submit-course-paper-token")
	@ApiOperationSupport(order = 19)
	@ApiOperation(value = "获取提交的权限", notes = "传入releaseId")
	public R getSubmitCoursePaperToken(Long courseId, Long paperAnswerId) {
		PaperReleaseEntity releaseEntity = releaseService.getOne(Wrappers.<PaperReleaseEntity>query().lambda()
			.eq(PaperReleaseEntity::getCourseId, courseId).eq(PaperReleaseEntity::getExamineType, 1));
		if (null == releaseEntity) throw new TrainingServiceException("该课程未设置课程测试,请联系管理员");
		String key = DigestUtil.encrypt(RedisKeyConstant.EXAM_KEY_PREFIX + courseId.toString() + AuthUtil.getUserId().toString() + System.currentTimeMillis());
		bladeRedis.set(key, paperAnswerId);
		return R.data(key);
	}


	/**
	 * 学生提交课程测试的试卷
	 * 自动计算除简答题外的分数
	 */
	@PostMapping("/submit-course-paper")
	@ApiOperationSupport(order = 20)
	@ApiOperation(value = "学生提交课程测试的试卷", notes = "传入整张试卷")
	@Transactional(rollbackFor = Exception.class)
	public R autoCalculateCourseScore(@RequestBody @Valid PaperReleaseVO paperRelease) {
		Long paperAnswerId = bladeRedis.get(paperRelease.getSubmitToken());
		if (null == paperAnswerId) throw new TrainingServiceException("请勿重复提交");
		Long userId = AuthUtil.getUserId();
		//少选是否给一半分数
		Integer multipleScoreSetting = paperRelease.getMultipleScoreSetting();

		//试卷分数
		int score = 0;

		//考试人员的作答及试题
		List<PaperReleaseDetailVO> paperReleaseDetailVOList = paperRelease.getPaperReleaseDetailList();

		//考试人员的作答选项
		List<PaperAnswerOptionEntity> paperAnswerEntityList = new ArrayList<>();

		//将题目按照题目类型分组
		Map<Integer, List<PaperReleaseDetailVO>> releaseDetailGroupMap = paperReleaseDetailVOList.stream().collect(Collectors.groupingBy(PaperReleaseDetailVO::getQuestionType));

		//计算分数
		for (Integer key : releaseDetailGroupMap.keySet()) {
			//策略模式
			CalculateScoreStrategy strategy = null;
			switch (key) {
				case SINGLE:
					strategy = new CalculateSingleChoice();
					break;
				case MULTIPLE:
					strategy = new CalculateMultipleChoice();
					break;
				case JUDGE:
					strategy = new CalculateJudgeQuestion();
					break;
				case FILL:
					strategy = new CalculateClozeQuestion();
					break;
				case SHORT:
					strategy = new CalculateShortAnswer();
					break;
				default:
					throw new TrainingServiceException("没有匹配到题目类型");
			}
			CalculateScoreContext context = new CalculateScoreContext(strategy);
			score += context.executeStrategy(releaseDetailGroupMap.get(key), paperAnswerEntityList, paperAnswerId, multipleScoreSetting);
		}

		//保存用户作答记录
		answerOptionService.saveBatch(paperAnswerEntityList);

		//修改用户的分数  根据创建时间降序，则修改的肯定是最新分数
		PaperAnswerEntity paperAnswerEntity = answerService.list(Wrappers.<PaperAnswerEntity>query().lambda()
			.eq(PaperAnswerEntity::getPaperReleaseId, paperRelease.getId())
			.eq(PaperAnswerEntity::getUserId, userId)
			.eq(PaperAnswerEntity::getExamineType, 1)
			.orderByDesc(PaperAnswerEntity::getExamStartTime)).get(0);

		paperAnswerEntity.setScore((float) score);
		//表示已交卷
		paperAnswerEntity.setStatus(1);
		paperAnswerEntity.setExamEndTime(new Date());
		answerService.updateById(paperAnswerEntity);

		//查询是否合格
		if (score >= paperRelease.getPassingScore()) { //如果合格，发放该课程的学时和积分
			CourseEntity courseEntity = courseService.getById(paperRelease.getCourseId());
			IntegralLogVO vo = integralLogService.generalIntegralByCourse(courseEntity);
			vo.setUserId(userId);
			integralLogService.addIntegral(vo);
		}

		return R.data(bladeRedis.del(paperRelease.getSubmitToken()));
	}
}
